package com.chenfan.ccp.plug.business.send;

import com.chenfan.ccp.plug.business.entity.ActionLog;
import com.chenfan.ccp.plug.business.entity.BusinessLog;
import com.chenfan.ccp.util.start.ApplicationContextUtil;
import com.chenfan.ccp.utils.TraceIdUtil;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.AsyncResult;

import java.lang.reflect.InvocationTargetException;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicLong;

import static com.chenfan.ccp.plug.business.constants.Constants.LOG_EXISTS;
import static com.chenfan.ccp.plug.business.constants.Constants.USER_ID;

/**
 * @author xutong
 */
@Slf4j
public abstract class AbstractLogSender implements LogSender {
    private static Cache<String, String> LOG_REPEAT_CACHE = Caffeine.newBuilder()
            .expireAfterWrite(1, TimeUnit.SECONDS).maximumSize(1000)
            .build();
    @Value("${spring.application.name:''}")
    private String serviceName;
    protected static final ExecutorService futureExecutor = new ThreadPoolExecutor
            (2, 2
                    , 200, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(10000), new ThreadFactory() {
                private final ThreadGroup group;

                private final AtomicLong threadCount = new AtomicLong(0);
                private final StringBuilder builder = new StringBuilder("futureExecutor");

                {
                    SecurityManager manager = System.getSecurityManager();
                    group = (manager == null ? Thread.currentThread().getThreadGroup() : manager.getThreadGroup());
                }


                @Override
                public Thread newThread(Runnable r) {
                    builder.substring(0, "name".length());
                    Thread thread = new Thread(group, r, builder.append(threadCount.incrementAndGet()).toString());
                    if (thread.isDaemon()) {
                        thread.setDaemon(true);
                    }
                    thread.setPriority(Thread.NORM_PRIORITY);

                    return thread;
                }
            }, new ThreadPoolExecutor.CallerRunsPolicy());

    @Override
    public Future<Boolean> send(ActionLog actionLog, String url) {
        BusinessLog businessLog = new BusinessLog();
        BeanUtils.copyProperties(actionLog, businessLog);
        List<ActionLog.ActionData> actionList = actionLog.getActionList();
        BeanUtils.copyProperties(actionList, businessLog);
        log.info("发送业务日志={}", businessLog);
        businessLog.setServiceName(serviceName);
        businessLog.setIp(ApplicationContextUtil.getLocalIp());
        businessLog.setTraceId(TraceIdUtil.getTraceId());
        businessLog.setOperator(USER_ID.get().getAccount());
        businessLog.setTenantId(USER_ID.get().getTenantId());
        businessLog.setOperationTime(new Date());
        try {
            businessLog.setRequestUrl(url);
            AsyncResult<Boolean> x = repeatedCheck(businessLog);
            if (x != null) {
                return x;
            }
            return this.asyncSend(businessLog);
        } catch (Exception e) {
            log.error("记录业务日志失败", e);
            return new AsyncResult<>(false);
        }
    }

    private AsyncResult<Boolean> repeatedCheck(BusinessLog businessLog) {
        String ifPresent = LOG_REPEAT_CACHE.getIfPresent(businessLog.toString());
        if (Objects.isNull(ifPresent)) {
            LOG_REPEAT_CACHE.put(businessLog.toString(), LOG_EXISTS);
        } else {
            //已经发送过了
            return new AsyncResult<>(true);
        }
        return null;
    }

    public  Future<Boolean> asyncSend(BusinessLog businessLog) {
        return futureExecutor.submit(() -> this.send(businessLog));
    }

    public abstract boolean send(BusinessLog businessLog) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException;
}
